/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package pwned.memory;

import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import pwned.objects.Target;
import pwned.objects.UD;
import pwned.objects.XYZ;
import utils.MathUtils;

/**
 *
 * @author Zlobniy
 */
public class Scanner implements Runnable{
    
    private final List<Target> targets = new ArrayList();
    private Integer count = 0;

    private boolean running = false;
    private final List<Integer> offsets;
    
    private final int rights = 0x0010 + 0x0020 + 0x0008;
    private final int DistXYParam = 320;
    private final int DistZParam = 250;
    
    private final int baseDLL = 0x10000000;
    private final String processName = "dfuw.exe";
    private final List<Long> processId = MyKernel32Impl.FindProcessId(processName);
    private final int readSize = 4;
    private final int readOffset = 0;
    private final int m = 65536;
    
    static MyKernel32 myKernel32 = (MyKernel32)Native.loadLibrary("kernel32", MyKernel32.class);
    static Ud07 udm = (Ud07)Native.loadLibrary("ml07", Ud07.class); // D:\\Coolstory\\
    
    private Long timeBetweenScan = 1000l;
    
    public Scanner( List<Integer> offsets ){
        this.offsets = offsets;
    }

    interface Ud07 extends Library
    {
        void new_scan(int pid, int valX, int valY, int valZ, int distXY, int distZ, int[] allX);
    }
    
    @Override
    public void run() {
        
        final Memory output = new Memory(readSize);
        final int pid = processId.get(1).intValue();
        final Pointer openedProcess = myKernel32.OpenProcess(rights, true, pid);
        while(true){
            System.out.println("Scanning...");
            while( running ){
                System.out.println("Run and wait "+timeBetweenScan+ " mills ");
                    scann( pid, openedProcess, output, offsets );

                try {

                    Thread.sleep( timeBetweenScan );

                } catch (InterruptedException ex) {
                    Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, null, ex);
                }
            
            }
            
            try {
                
                Thread.sleep( 500 );
                
            } catch (InterruptedException ex) {
                Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, null, ex);
            }
            
        }
        
           
        
    }
    
    private void scann( final int pid, final Pointer openedProcess, final Memory output, List<Integer> offsets ){
        
        int[] dllStruct = new int[10000];
        
        Integer address = findMyself( openedProcess, output, offsets );
        XYZ xyz = findMyXYZ(openedProcess, output, address);
        
        udm.new_scan(pid, xyz.getX(), xyz.getY(), xyz.getZ(), DistXYParam, DistZParam, dllStruct);
        
        updateTargets( dllStruct, openedProcess, output, xyz );
        count = getCount( dllStruct );
    }
    
    private void updateTargets( int[] dllStruct, final Pointer openedProcess, final Memory output, final XYZ xyz ){
        
        List<Integer> adresses = getDllValues(dllStruct);
        
        List<Target> oldTargets = new ArrayList<>(targets.size());
        List<Target> newTargets = new ArrayList<>(100);
        
        oldTargets.addAll(targets);
        
        for( Integer adress : adresses ){
            
            float ix1976 = 0.0f;
            boolean success = myKernel32.ReadProcessMemory(openedProcess, adress + 1976, output, readSize, new IntByReference(0));
            if( success ){
                ix1976 = Math.abs(output.getFloat(readOffset));
            }
            
            float ix1980 = 0.0f;
            success = myKernel32.ReadProcessMemory(openedProcess, adress + 1980, output, readSize, new IntByReference(0));
            if( success ){
                ix1980 = Math.abs(output.getFloat(readOffset));
            }
            
            float ix1984 = 0.0f;
            success = myKernel32.ReadProcessMemory(openedProcess, adress + 1984, output, readSize, new IntByReference(0));
            if( success ){
                ix1984 = Math.abs(output.getFloat(readOffset));
            }
            
            float sumQ = ix1976 * ix1976 + ix1984 * ix1984 + ix1980 * ix1980;
            
            if( sumQ > 0.97 && sumQ < 1.03 && ix1976 > 0 && ix1976 < 1 && ix1984 > 0 && ix1984 < 1){
                
                long ix1960 = 0;
                success = myKernel32.ReadProcessMemory(openedProcess, adress + 1960, output, 8, new IntByReference(0));
                if( success ){
                    ix1960 = MathUtils.unsToS( output.getInt(readOffset) );
                }
                
                long iy1960 = 0;
                success = myKernel32.ReadProcessMemory(openedProcess, adress + 8 + 1960, output, 8, new IntByReference(0));
                if( success ){
                    iy1960 = MathUtils.unsToS( output.getInt(readOffset) );
                }
                
                long iz1960 = 0;
                success = myKernel32.ReadProcessMemory(openedProcess, adress + 4 + 1960, output, 8, new IntByReference(0));
                if( success ){
                    iz1960 = MathUtils.unsToS( output.getInt(readOffset) );
                }
                
                int dx = (int) (xyz.getX() - ix1960);
                int aDX = Math.abs(dx);
                int dy = (int) (xyz.getY() - iy1960);
                int aDY = Math.abs(dy);
                
                int dist = (int) Math.sqrt( (aDX * aDX) + (aDY * aDY) );
                
                Target target = isHaveTarget( adress + 1960, oldTargets );
                if( target != null ){
                    
                    int dx1 = (int) (xyz.getX() - ix1960);
                    int aDX1 = Math.abs(dx1);
                    int dy1 = (int) (xyz.getY() - iy1960);
                    int aDY1 = Math.abs(dy1);

                    int newDist = (int) Math.sqrt( (aDX1 * aDX1) + (aDY1 * aDY1) );
                    target.setDistance(newDist);
                    target.setNewTarget(false);
                    newTargets.add( target ); 
                    
                }else{
                    
                    Target newTarget = new Target();
                    newTarget.setX( adress + 1960 );
                    newTarget.setY( adress + 1960 + 8 );
                    newTarget.setZ( adress + 1960 + 4 );
                    newTarget.setDistance(dist);
                    newTarget.setNewTarget(true);
                    newTarget.setB2(false);
                    newTarget.setB3(false);

                    newTargets.add( newTarget );
                }
            
            }
    
        }
        
        targets.clear();
        targets.addAll(newTargets);
        
    }
    
    private Target isHaveTarget( int address, List<Target> oldTargets ){
        for( Target target : oldTargets ){
            if( target.isTarget(address) ){
                return target;
            }
        }
        return null;
    }
    
    private Integer findMyself( final Pointer openedProcess, final Memory output, List<Integer> offsets ){
        
        Integer result = 0;
        int value = 0;
        for( Integer val : offsets ){
            
            if( value == 0 ) value = baseDLL;
            
            int address = value + val;
            
            boolean success = myKernel32.ReadProcessMemory(openedProcess, address, output, readSize, new IntByReference(0));
            if( success ){
                value = output.getInt(readOffset);
            }else{
                
            }
            result = value;
        }
        
        
        return result;
    }
    
    private XYZ findMyXYZ( final Pointer openedProcess, final Memory output, final int address ){
        
        XYZ xyz = new XYZ();
        
        boolean success = myKernel32.ReadProcessMemory(openedProcess, address + 0x14, output, readSize, new IntByReference(0));
        if( success ){
            xyz.setX(output.getInt(readOffset));
        }
        
        success = myKernel32.ReadProcessMemory(openedProcess, address + 0x14 + 0x8, output, readSize, new IntByReference(0));
        if( success ){
            xyz.setY(output.getInt(readOffset));
        }
        
        success = myKernel32.ReadProcessMemory(openedProcess, address + 0x14 + 0x4, output, readSize, new IntByReference(0));
        if( success ){
            xyz.setZ( (int) (output.getInt(readOffset) + (0.5 * m)) );
        }
        
        return xyz;
    }
    
    private UD findMyUd( final Pointer openedProcess, final Memory output, final int address ){
    
        UD ud = new UD();
        
        boolean success = myKernel32.ReadProcessMemory(openedProcess, address, output, readSize, new IntByReference(0));
        if( success ){
            ud.setUd(output.getFloat(readOffset));
        }
        
        success = myKernel32.ReadProcessMemory(openedProcess, address + 0x4, output, readSize, new IntByReference(0));
        if( success ){
            ud.setNs(output.getFloat(readOffset));
        }
        
        success = myKernel32.ReadProcessMemory(openedProcess, address - 0x4, output, readSize, new IntByReference(0));
        if( success ){
            ud.setWe(output.getFloat(readOffset));
        }
        
        return ud;
        
    }
    
    private List getDllValues( final int[] dllStructur ){
        final List<Integer> values = new ArrayList<>();
        for( int i = 1 ; i < dllStructur.length ; i++){
            int val = dllStructur[i];
            if( val > 0 ){
                values.add(val);
            }
        }
        return values;
    }

    public List<Target> getTargets() {
        return targets;
    }
    
    private Integer getCount( final int[] dllStructur ){
        return dllStructur[0];
    }
    
    public Integer getCount(  ){
        return count;
    }

    public boolean isRunning() {
        return running;
    }

    public void setRunning(boolean running) {
        this.running = running;
    }

    public Long getTimeBetweenScan() {
        return timeBetweenScan;
    }

    public void setTimeBetweenScan(Long timeBetweenScan) {
        this.timeBetweenScan = timeBetweenScan;
    }
    
    
    
}
